package com.zy.je.common.core.utils.probailityhit;

import com.zy.je.common.core.utils.CheckUtil;
import com.zy.je.common.core.utils.collect.ListUtil;
import com.zy.je.common.core.utils.lang.NumberUtil;
import com.zy.je.common.core.utils.lang.ObjectUtil;
import com.zy.je.common.core.utils.lang.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.hutool.json.JSONUtil;

import java.math.BigDecimal;
import java.util.List;
import java.util.Random;

/**
 * 概率命中工具<br>
 * 使用步骤：<br>
 * 1、将源数据格式化为统一格式（调用ProbabilityHitUtil.convertToProbabilityHitData方法）<br>
 * 2、设置每项数据的概率值（为ProbabilityHitData.probability字段赋值）<br>
 * 3、根据各项数据的概率值填充概率区间（调用ProbabilityHitUtil.fillProbabilityRange）<br>
 * 4、根据概率进行命中（调用ProbabilityHitUtil.matchByProbability）<br>
 *
 * @author zy
 * @decription
 */
@Slf4j
public class ProbabilityHitUtil {
    public static void main(String[] args) {
        List<Integer> list = ListUtil.newArrayList(2, 3, 2, 8, 1, 11);
        List<ProbabilityHitData<Integer>> dataList = convertToProbabilityHitData(list);
        //计算概率
        //对权重求和
        BigDecimal sumWeight = NumberUtil.calcSum(list, it -> it, null, BigDecimal.ZERO);
        //计算每一项的概率
        dataList.forEach(it -> it.setProbability(
                NumberUtil.div(ObjectUtil.toBigDecimal(it.getData()), sumWeight)
        ));
        //填充概率区间
        ProbabilityHitUtil.fillProbabilityRange(dataList, 3);
        //命中
        ProbabilityHitData<Integer> hitData = ProbabilityHitUtil.matchByProbability(dataList);
        System.out.println(StringUtil.fmtWithParam("hitData={}", JSONUtil.toJsonStr(hitData)));
    }

    /**
     * 1、将源数据格式化为统一格式
     *
     * @param dataList
     * @param <T>
     * @return
     */
    public static <T> List<ProbabilityHitData<T>> convertToProbabilityHitData(List<T> dataList) {
        List<ProbabilityHitData<T>> retList = ListUtil.newArrayList();
        ListUtil.ifNull2EmptyList(dataList)
                .forEach(it -> {
                    ProbabilityHitData<T> data = ProbabilityHitData.<T>builder()
                            .data(it)
                            .build();
                    retList.add(data);
                });
        return retList;
    }

    /**
     * 3、根据各项数据的概率值填充概率区间
     *
     * @param dataList
     * @param scale
     * @param <T>
     */
    public static <T> void fillProbabilityRange(List<ProbabilityHitData<T>> dataList, Integer scale) {
        /**
         * 注意Inter的最大值，避免因scale太大发生溢出
         */
        final BigDecimal[] rangeL = {ObjectUtil.toBigDecimal(0)};
        Integer scaleValue = NumberUtil.pow(10, scale).intValue();
        dataList
                .forEach(it -> {
                    //计算概率区间
                    BigDecimal rangeR = NumberUtil.add(rangeL[0], it.getProbability());
                    it.setProbabilityRange(ListUtil.newArrayList(
                                    NumberUtil.mul(rangeL[0], scaleValue).intValue(),
                                    NumberUtil.mul(rangeR, scaleValue).intValue()
                            )
                    );
                    rangeL[0] = rangeR;
                });
    }

    /**
     * 4、按概率进行匹配
     *
     * @param dataList
     * @param <T>
     * @return
     */
    public static <T> ProbabilityHitData<T> matchByProbability(List<ProbabilityHitData<T>> dataList) {
        Integer integer = matchIndexByProbabilityRange(dataList);
        return ListUtil.get(dataList, integer, null);
    }

    private static <T> Integer findProbabilityRangeR(List<ProbabilityHitData<T>> dataList) {
        //因为数据列表是有序的，反向找到第一个概率区间右边界值 就是目标值
        for (int i = dataList.size() - 1; i >= 0; i--) {
            ProbabilityHitData it = dataList.get(i);
            List<Integer> range = it.getProbabilityRange();
            if (!CheckUtil.isBlank(range)
                    && range.size() >= 2) {
                return range.get(1);
            } else {
                log.info("获取概率区间右边界，概率区间数据不正确：{}", JSONUtil.toJsonStr(it));
            }
        }
        return -1;
    }

    private static Integer hitWithRange(Integer rangeR) {
        //nextInt返回值为[0,rangeR),加1后，返回值区间就变成了[1,rangeR]
        return (new Random()).nextInt(rangeR) + 1;
    }

    private static <T> Integer matchIndexByProbabilityRange(List<ProbabilityHitData<T>> dataList) {
        Integer rangeR = findProbabilityRangeR(dataList);
        if (rangeR < 0) {
            log.info("根据概率命中，未获取到获取概率区间右边界");
            log.debug("根据概率命中，未获取到获取概率区间右边界,dataList={}", JSONUtil.toJsonStr(dataList));
        } else {
            Integer hitValue = hitWithRange(rangeR);
            for (int i = 0; i < dataList.size(); i++) {
                ProbabilityHitData it = dataList.get(i);
                List<Integer> range = it.getProbabilityRange();
                if (!CheckUtil.isBlank(range)
                        && range.size() >= 2) {
                    if (range.get(0) < hitValue && hitValue <= range.get(1)) {
                        return i;
                    }
                } else {
                    log.info("根据概率命中，概率区间数据不正确：{}", JSONUtil.toJsonStr(it));
                }
            }
        }
        log.info("根据概率命中，未匹配到数据");
        log.debug("根据概率命中，未匹配到数据：rangeR={},dataList={}", rangeR, JSONUtil.toJsonStr(dataList));
        return -1;
    }


}
